module pflanza.plantdistributor;

import pflanza.plants;
import pflanza.terrain;
import std.stream;
import std.string;
import std.math;
import std.stdio;
import utils.configfile;
import utils.randval;
import utils.vector2;
import utils.vector3;
import utils.mt19937ar;

struct PlantInstDef {
    char[] plantId;
    RandValf scale;
    RandVali count;

    public static PlantInstDef opCall(char[] plantId, ConfigNode piNode) {
        PlantInstDef ret;
        ret.plantId = plantId;
        ret.scale = RandValf(piNode.getStringValue("scale","1"));
        ret.count = RandVali(piNode.getStringValue("count","0"));
        return ret;
    }
}

struct PlantInstance {
    float scale;
    Vector2i pos;    //in heightmap coordinates
    float rot;
    char[] objName;
    char[] plantClass;

    public static PlantInstance opCall(float scale, float rot, Vector2i pos,
        char[] objName, char[] plantClass)
    {
        PlantInstance ret;
        ret.scale = scale;
        ret.rot = rot;
        ret.pos = pos;
        ret.objName = objName;
        ret.plantClass = plantClass;
        return ret;
    }
}

class PlantDistributor {
    private {
        Terrain mTerrain;
        Plants mPlants;
        RandVali[char[]][char[]] mDistances;   //harhar
        PlantInstDef[] mInstDef;

        PlantInstance[] mInstances;
    }

    this(Plants p, Terrain t) {
        mPlants = p;
        mTerrain = t;
    }

    public void fromNode(ConfigNode distNode) {
        foreach (char[] id, ConfigNode n; distNode.getSubNode("instances",true)) {
            if (id in mPlants.plants)
                mInstDef ~= PlantInstDef(id, n);
        }

        foreach (char[] idClass, ConfigNode n; distNode.getSubNode("distances",true)) {
            foreach (char[] idDistTo, char[] value; n) {
                RandVali ri = RandVali(value);
                //make sure both RandVal instances get the same actual value
                ri.value();
                //fill the whole matrix, even if redundant values are not in cfg file
                mDistances[idClass][idDistTo] = ri;
                mDistances[idDistTo][idClass] = ri;
            }
        }
    }

    public void generate() {
        const maxRandCycles = 1000;
        mInstances = null;

        RandVali rotSelect = RandVali(0,359);
        RandVali posSelect = RandVali(0,mTerrain.terrain.res-1);
        foreach (inout PlantInstDef pidef; mInstDef) {
            writefln("Adding %d %s to scene",pidef.count.value,pidef.plantId);
            //add <count> plants for each instance, using random scale and variation
            Plant curPlant = mPlants.plants[pidef.plantId];
            //variation selector, select var. from 0 to the # actually generated
            RandVali varSelect = RandVali(0,curPlant.variations.value-1);
            for (int i = 0; i < pidef.count.value; i++) {
                int varIdx = varSelect.sample();
                int rot = rotSelect.sample();
                float scale = pidef.scale.sample();
                //try <maxRandCycles> times to insert the current plant without overlap
                //xxx I couldn't think of any other algorithm...
                randLoop: for (int rc = 0; rc < maxRandCycles; rc++) {
                    Vector2i pos = Vector2i(posSelect.sample,posSelect.sample);
                    //check if there is an overlap with any plant previously inserted
                    foreach (inout PlantInstance insPlant; mInstances) {
                        float dist = sqrt(cast(float)(pos-insPlant.pos).quad_length());
                        if (dist < mDistances[curPlant.pclass][insPlant.plantClass].value) {
                            //overlap found -> next try
                            continue randLoop;
                        }
                    }
                    //no overlap -> position is ok
                    PlantInstance pi = PlantInstance(scale,rot,pos,
                        mPlants.variationName(pidef.plantId,varIdx),curPlant.pclass);
                    mInstances ~= pi;
                    break;
                }
            }
        }
    }

    public void writeScript(Stream st) {
        st.writefln("# Plants, instantiations");
        foreach (inout PlantInstance insPlant; mInstances) {
            Vector3f pos3d = mTerrain.terrain.hmapTo3d(insPlant.pos);
            st.writefln("scale %f",insPlant.scale);
            st.writefln("translate %f %f %f",pos3d.x,pos3d.y,pos3d.z);
            st.writefln("rotate 0 %f 0",insPlant.rot);
            st.writefln("insert %s",insPlant.objName);
        }
    }
}
